package semaphores;

/**
 * The class synchronizes the reading and the writing actions.
 *
 * @author Manujlov Maxim
 */
public final class SmartSemaphore implements ISmartSemaphore {
  /**
   * Current quantity of the threads
   */
  private long count;

  /**
   * Maximal quantity of the threads
   */
  private final long maxCount;

  /**
   * True, if there is an exclusive session
   */
  private boolean exclusive;

  /**
   * The constructor of the semaphore with the limitation of the threads quantity
   *
   * @param maxCount maximal threads quantity
   */
  public SmartSemaphore(long maxCount) {
    count = 0;
    exclusive = false;
    this.maxCount = maxCount;
  }

  /**
   * The constructor of the semaphore without the limitation of the threads quantity
   */
  public SmartSemaphore() {
    count = 0;
    exclusive = false;
    this.maxCount = Long.MAX_VALUE;
  }

  /**
   * The function turns the semaphore down
   */
  public synchronized void down() {
    while ((exclusive) || (count == maxCount)) {
      try {
        wait();
      }
      catch (InterruptedException e) {}
    }
    count++;
  }

  /**
   * The function turns the semaphore up
   */
  public synchronized void up() {
    if ((exclusive) || (count == 0)) return;
    count--;
    notifyAll();
  }

  /**
   * The function turns the semaphore down and starts an exclusive session
   */
  public synchronized void exclusiveDown() {
    while (count > 0) {
      try {
        wait();
      }
      catch (InterruptedException e) {}
    }
    count++;
    exclusive = true;
  }

  /**
   * The function turns the semaphore up and finishes an exclusive session
   */
  public synchronized void exclusiveUp() {
    if (!exclusive) return;
    count--;
    exclusive = false;
    notifyAll();
  }

  /**
   * The function executes a reading action
   *
   * @param r action
   */
  public void runReadAction(Runnable r) {
    down();
    try {
      r.run();
    }
    finally {
      up();
    }
  }

  /**
   * The function executes a writing action
   *
   * @param r action
   */
  public void runWriteAction(Runnable r) {
    exclusiveDown();
    try {
      r.run();
    }
    finally {
      exclusiveUp();
    }
  }
}
